package org.thanos.baseline.utils;

import org.apache.commons.lang3.Range;

import java.time.LocalDateTime;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Comparator;
import java.util.LinkedList;
import java.util.List;
import java.util.Objects;
import java.util.Optional;
import java.util.function.Function;

public class TimeRangeUtils {

    public static final Function<Integer, List<Range<Integer>>> intervalRangeListForBob = interval -> {
        List<Range<Integer>> timeRangeList = new ArrayList<>();
        for (int i = 1; i <= 60 / interval; i++) {
            int a = (i - 1) * interval;
            int b = i * interval - 1;
            if (i * interval - 1 >= 60) {
                b = 59;
            }
            timeRangeList.add(Range.between(a, b));
        }
        return timeRangeList;
    };

    // 用来处理季度
    private static final List<Range<Integer>> quarterRangeListForBob = Arrays.asList(Range.between(1, 3),
            Range.between(4, 6), Range.between(7, 9), Range.between(10, 12));
    private static final Function<LocalDateTime, LocalDateTime> walkAQuarter = bob -> {
        Integer month = bob.getMonthValue();
        Optional<Range<Integer>> hitRange = quarterRangeListForBob.stream().filter(r -> r.contains(month)).findFirst();
        LocalDateTime newBob = LocalDateTime.of(bob.getYear(), hitRange.get().getMinimum(), 1, 0, 0, 0);
        return newBob.plusMonths(3);
    };

    private static final Function<LocalDateTime, LocalDateTime> walkAHour = bob -> TimePointUtils.firstSecondOfTheHour(bob).plusHours(1);
    private static final Function<LocalDateTime, LocalDateTime> walkADay = bob -> TimePointUtils.firstSecondOfTheDay(bob).plusDays(1);
    private static final Function<LocalDateTime, LocalDateTime> walkAWeek = bob -> TimePointUtils.firstSecondOfTheWeek(bob).plusWeeks(1);
    private static final Function<LocalDateTime, LocalDateTime> walkAMonth = bob -> TimePointUtils.firstSecondOfTheMonth(bob).plusMonths(1);
    private static final Function<LocalDateTime, LocalDateTime> walkAYear = bob -> TimePointUtils.firstSecondOfTheYear(bob).plusYears(1);

    private TimeRangeUtils() {
    }

    /**
     * 得到一个时间范围
     *
     * @param from 起始时间点
     * @param to   结束时间点
     * @return 时间范围
     */
    public static Range<LocalDateTime> range(LocalDateTime from, LocalDateTime to) {
        return Range.between(from, to, Comparator.naturalOrder());
    }

    /**
     * 时间范围内的下一天
     *
     * @param range     时间范围
     * @param timePoint 当前时间点
     * @return 下一天
     */
    public static Optional<LocalDateTime> nextDay(Range<LocalDateTime> range, LocalDateTime timePoint) {
        if (range.contains(timePoint)) {
            return Optional.of(timePoint.plusDays(1));
        } else {
            return Optional.empty();
        }
    }

    /**
     * 时间范围内的前一天
     *
     * @param range     时间范围
     * @param timePoint 当前时间点
     * @return 前一天
     */
    public static Optional<LocalDateTime> preDay(Range<LocalDateTime> range, LocalDateTime timePoint) {
        if (range.contains(timePoint)) {
            return Optional.of(timePoint.minusDays(1));
        } else {
            return Optional.empty();
        }
    }

    /**
     * 行走的鲍勃
     *
     * @param range   时间范围
     * @param stepFoo 步长函数
     * @return 鲍勃在时间范围内行走的轨迹
     */
    private static List<LocalDateTime> walkingBob(Range<LocalDateTime> range, Function<LocalDateTime, LocalDateTime> stepFoo) {
        LocalDateTime bob = range.getMinimum();
        List<LocalDateTime> timeAxis = new LinkedList<>();
        while (range.contains(bob)) {
            timeAxis.add(bob);
            bob = stepFoo.apply(bob);
        }
        return timeAxis;
    }

    /**
     * 以采集间隔为步长计算并得到时间范围内的时间轴序列
     *
     * @param range 时间范围
     * @return 时间轴序列
     */
    public static List<LocalDateTime> timeAxisStepByInterval(Range<LocalDateTime> range, Integer interval) {
        if (Objects.isNull(interval)) {
            interval = 15;
        }
        Integer finalInterval = interval;
        Function<LocalDateTime, LocalDateTime> walkAIntervalSS = bob -> {
            Integer min = bob.getMinute();
            Optional<Range<Integer>> hitRange = intervalRangeListForBob.apply(finalInterval).stream().filter(r -> r.contains(min)).findFirst();
            LocalDateTime newBob = LocalDateTime.of(bob.getYear(), bob.getMonthValue(), bob.getDayOfMonth(), bob.getHour(), hitRange.get().getMinimum(), 0);
            return newBob.plusMinutes(finalInterval);
        };
        return walkingBob(range, walkAIntervalSS);
    }

    /**
     * 以小时为步长计算并得到时间范围内的时间轴序列
     *
     * @param range 时间范围
     * @return 时间轴序列
     */
    public static List<LocalDateTime> timeAxisStepByHour(Range<LocalDateTime> range) {
        return walkingBob(range, walkAHour);
    }

    /**
     * 以天为步长计算并得到时间范围内的时间轴序列
     *
     * @param range 时间范围
     * @return 时间轴序列
     */
    public static List<LocalDateTime> timeAxisStepByDay(Range<LocalDateTime> range) {
        return walkingBob(range, walkADay);
    }

    /**
     * 以周为单位计算并得到时间范围内的时间轴序列
     *
     * @param range 时间范围
     * @return 时间轴序列
     */
    public static List<LocalDateTime> timeAxisStepByWeek(Range<LocalDateTime> range) {
        return walkingBob(range, walkAWeek);
    }

    /**
     * 以月为单位计算得到时间范围内的时间轴序列
     *
     * @param range 时间范围
     * @return 时间轴列
     */
    public static List<LocalDateTime> timeAxisStepByMonth(Range<LocalDateTime> range) {
        return walkingBob(range, walkAMonth);
    }

    /**
     * 以季度为单位计算得到时间范围内的时间轴序列
     *
     * @param range 时间范围
     * @return 时间轴序列
     */
    public static List<LocalDateTime> timeAxisStepByQuarter(Range<LocalDateTime> range) {
        return walkingBob(range, walkAQuarter);
    }

    /**
     * 以年为单位计算得到时间范围内的时间轴序列
     *
     * @param range 时间范围
     * @return 时间轴序列
     */
    public static List<LocalDateTime> timeAxisStepByYear(Range<LocalDateTime> range) {
        return walkingBob(range, walkAYear);
    }

    /**
     * 依据以采集间隔为单位的时间轴生成时间范围闭区间
     *
     * @param timeAxis 以采集间隔为单位的时间轴
     * @return 时间范围闭区间
     */
    public static List<Range<LocalDateTime>> timeRangeAxisOfInterval(List<LocalDateTime> timeAxis) {
        List<Range<LocalDateTime>> temp = new LinkedList<>();
        return timeAxis.stream().reduce(temp, (list, item) -> {
            list.add(TimeRangeUtils.range(item, item.plusMinutes(15)));
            return list;
        }, (finalResult, result) -> {
            finalResult.addAll(result);
            return finalResult;
        });
    }

    /**
     * 依据以天为单位的时间轴生成时间范围闭区间
     *
     * @param timeAxis 以天为单位的时间轴
     * @return 时间范围闭区间
     */
    public static List<Range<LocalDateTime>> timeRangeAxisOfDay(List<LocalDateTime> timeAxis) {
        List<Range<LocalDateTime>> temp = new LinkedList<>();
        return timeAxis.stream().reduce(temp, (list, item) -> {
            list.add(TimeRangeUtils.range(item, TimePointUtils.lastSecondOfTheDay(item)));
            return list;
        }, (finalResult, result) -> {
            finalResult.addAll(result);
            return finalResult;
        });
    }

    /**
     * 依据以周为单位的时间轴生成时间范围闭区间
     *
     * @param timeAxis 以周为单位的时间轴
     * @return 时间范围闭区间
     */
    public static List<Range<LocalDateTime>> timeRangeAxisOfWeek(List<LocalDateTime> timeAxis) {
        List<Range<LocalDateTime>> temp = new LinkedList<>();
        return timeAxis.stream().reduce(temp, (list, item) -> {
            list.add(TimeRangeUtils.range(item, TimePointUtils.lastSecondOfTheWeek(item)));
            return list;
        }, (finalResult, result) -> {
            finalResult.addAll(result);
            return finalResult;
        });
    }

    /**
     * 依据以月为单位的时间轴生成时间范围闭区间
     *
     * @param timeAxis 以月为单位的时间轴
     * @return 时间范围闭区间
     */
    public static List<Range<LocalDateTime>> timeRangeAxisOfMonth(List<LocalDateTime> timeAxis) {
        List<Range<LocalDateTime>> temp = new LinkedList<>();
        return timeAxis.stream().reduce(temp, (list, item) -> {
            list.add(TimeRangeUtils.range(item, TimePointUtils.lastSecondOfTheMonth(item)));
            return list;
        }, (finalResult, result) -> {
            finalResult.addAll(result);
            return finalResult;
        });
    }

    /**
     * 依据以季度为单位的时间轴生成时间范围闭区间
     *
     * @param timeAxis 以季度为单位的时间轴
     * @return 时间范围闭区间
     */
    public static List<Range<LocalDateTime>> timeRangeAxisOfQuarter(List<LocalDateTime> timeAxis) {
        List<Range<LocalDateTime>> temp = new LinkedList<>();
        return timeAxis.stream().reduce(temp, (list, item) -> {
            list.add(TimeRangeUtils.range(item, TimePointUtils.lastSecondOfTheQuarter(item)));
            return list;
        }, (finalResult, result) -> {
            finalResult.addAll(result);
            return finalResult;
        });
    }

    /**
     * 依据以季度为单位的时间轴生成时间范围闭区间
     *
     * @param timeAxis 以季度为单位的时间轴
     * @return 时间范围闭区间
     */
    public static List<Range<LocalDateTime>> timeRangeAxisOfHour(List<LocalDateTime> timeAxis) {
        List<Range<LocalDateTime>> temp = new LinkedList<>();
        return timeAxis.stream().reduce(temp, (list, item) -> {
            list.add(TimeRangeUtils.range(item, TimePointUtils.lastSecondOfTheHour(item)));
            return list;
        }, (finalResult, result) -> {
            finalResult.addAll(result);
            return finalResult;
        });
    }

    /**
     * 依据以年为单位的时间轴生成时间范围闭区间
     *
     * @param timeAxis 以年为单位的时间轴
     * @return 时间范围闭区间
     */
    public static List<Range<LocalDateTime>> timeRangeAxisOfYear(List<LocalDateTime> timeAxis) {
        List<Range<LocalDateTime>> temp = new LinkedList<>();
        return timeAxis.stream().reduce(temp, (list, item) -> {
            list.add(TimeRangeUtils.range(item, TimePointUtils.lastSecondOfTheYear(item)));
            return list;
        }, (finalResult, result) -> {
            finalResult.addAll(result);
            return finalResult;
        });
    }

    /**
     * 输入起始时间，返回按天计算每日起始时间的列表
     *
     * @param start 算法预测的结果
     * @param end   截止时间
     * @return List<Range < LocalDateTime>> 每日起始时间的列表
     */
    public static List<LocalDateTime> getTimeRangeList(LocalDateTime start, LocalDateTime end, Integer aggregationType) {
        // 根据当前查询的时间范围，转换得到该时间范围内每天查询的时间范围
        Range<LocalDateTime> range = Range.between(start, end, LocalDateTime::compareTo);
        List<LocalDateTime> dailyStartTimes = null;
        switch (aggregationType) {
            case 0:
                dailyStartTimes = TimeRangeUtils.timeAxisStepByHour(range);
                break;
            case 1:
                dailyStartTimes = TimeRangeUtils.timeAxisStepByDay(range);
                break;
            case 2:
                dailyStartTimes = TimeRangeUtils.timeAxisStepByMonth(range);
                break;
        }
        // 获得该时间段内，每天的开始时间列表
        return dailyStartTimes;
    }
}
